// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 开始：建造计划9
// +建设计划9

package lockedfile

import (
	"io/fs"
	"math/rand"
	"os"
	"strings"
	"time"
)

// 打开独占使用文件会返回错误。
// 预期的错误字符串为：
// None
// -“打开/创建--文件已锁定”（cwfs、kfs）
// -“独占锁”（化石）
// -“独占使用文件已打开”（ramfs）
var lockedErrStrings = [...]string{
	"file is locked",
	"exclusive lock",
	"exclusive use file already open",
}

// 即使plan9不支持锁定/重新锁定/解锁功能
// 操作已经打开的文件，IsLocked仍然有意义：os.OpenFile
// 它本身可能会返回错误，表明具有ModeExclusive位的文件
// 集合已打开。
func isLocked(err error) bool {
	s := err.Error()

	for _, frag := range lockedErrStrings {
		if strings.Contains(s, frag) {
			return true
		}
	}

	return false
}

func openFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
	// 计划9使用模式位而不是显式锁定/解锁系统调用。
	// None
	// 根据http：
	// 对于I/O，在服务器的所有客户端上，一次仅通过一个fid执行。如果
	// 如果尝试第二次打开，则会绘制错误。“
	// None
	// 所以我们可以尝试打开一个锁定的文件，但如果它失败了，我们就只能自己打开了
	// 弄清楚它什么时候可用。我们将使用指数退避
	// 一些抖动和500ms的任意限制。

	// 如果该文件是由其他程序解包或创建的，则可能不是
	// 设置ModeExclusive位。在调用OpenFile之前设置它，以便
	// 可以确信一个成功的OpenFile意味着独占使用。
	if fi, err := os.Stat(name); err == nil {
		if fi.Mode()&fs.ModeExclusive == 0 {
			if err := os.Chmod(name, fi.Mode()|fs.ModeExclusive); err != nil {
				return nil, err
			}
		}
	} else if !os.IsNotExist(err) {
		return nil, err
	}

	nextSleep := 1 * time.Millisecond
	const maxSleep = 500 * time.Millisecond
	for {
		f, err := os.OpenFile(name, flag, perm|fs.ModeExclusive)
		if err == nil {
			return f, nil
		}

		if !isLocked(err) {
			return nil, err
		}

		time.Sleep(nextSleep)

		nextSleep += nextSleep
		if nextSleep > maxSleep {
			nextSleep = maxSleep
		}
		// 应用10%的抖动以避免同步冲突。
		nextSleep += time.Duration((0.1*rand.Float64() - 0.05) * float64(nextSleep))
	}
}

func closeFile(f *os.File) error {
	return f.Close()
}
